import java.awt.image.BufferedImage;

import java.awt.image.WritableRaster;

import edu.rit.pj.BarrierAction;
import edu.rit.pj.IntegerForLoop;
import edu.rit.pj.IntegerSchedule;
import edu.rit.pj.ParallelRegion;
import edu.rit.pj.ParallelTeam;


public class Morph {

	// This describes how often the GUI will redraw the morphed picture
	// Set this to a higher value, like 16, for larger jobs.
	public static int UPDATE_STEPS = 8;
	public static int inner_steps;

	//take a larger stepping factor for faster morphing
	public static int FAST_STEPPING = 4;

	// The total number of iterations may be as high as 256 becuase
	// the difference in color-intensity in some point may be 256 for some color
	public static int MORPH_ITERATIONS = 256/FAST_STEPPING + FAST_STEPPING;

	// After cropping, height/width should be equal for both images.
	public static int height;
	public static int width;

	public static BufferedImage morph;
	public static WritableRaster morphRaster;

	static int [] array1;
	static int [] array2;

	static MorphImg gui;

	public static int numThreads =1 ;
	public static boolean displayOff= false;

	private Morph(){}

	public static void setNumThreads(int threads)
	{
		numThreads = threads;
	}

	public static void setUpdateSteps(int steps)
	{
		UPDATE_STEPS = steps;
	}
	public static void setFastStepping(int steps)
	{
		FAST_STEPPING = steps;
		MORPH_ITERATIONS = 256/FAST_STEPPING + FAST_STEPPING;
	}

	public static void setDisplayOff(boolean c)
	{
		displayOff = c;
	}

	public static void MorphInit(BufferedImage image1, BufferedImage image2, MorphImg guiapp) {

		gui = guiapp;

		// Double check that we clipped the images at read in time
		// if not the same size
		assert(image1.getWidth() == image2.getWidth());
		assert(image1.getHeight() == image2.getHeight());
		height = image1.getHeight();
		width = image1.getWidth();


		array1 = image1.getRaster().getPixels(0, 0, width, height, (int[])null);
		array2 = image2.getRaster().getPixels(0, 0, width, height, (int[])null);

		// The gui displays the morphed image, which initially is the
		// "From" image
		morph = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
		// Return a  WritableRaster, an internal reprentation of the
		// image (class Raster), that we can modify
		morphRaster = morph.getRaster();
		//Copy the "from" image and update the raster
		morphRaster.setPixels(0, 0, width, height, array1);
		gui.updateMorphImage(morph, false);


		// Generate the 3D representations of the images
		//array1 = convertTo3DArray(image1pixels, width, height);
		//array2 = convertTo3DArray(image2pixels, width, height);
	}


	//redraws only if display mode is on
	static void redrawPicture(int[] array1)
	{

		if(!displayOff){
			//gui.freezeTimer();
			//int[] arr1D = convertTo1DArray(array1, width, height);
			morphRaster.setPixels(0, 0, width, height, array1);
			gui.updateMorphImage(morph, false);
			// update the reported timing
			gui.updateTimer();
			//gui.unFreezeTimer();

		}
	}

	public static BufferedImage parallelDoMorph() {



		final int last_iteration = MORPH_ITERATIONS % UPDATE_STEPS;
		//start the timer
		gui.startTimer();
		try {
			//create the threads to execute the code in parallel (data parallelism)
			new ParallelTeam(numThreads).execute(new ParallelRegion() {

				//run method for the parallel team; all the team threads call run(). This is where the execution of the parallel region code happens.
				@Override
				public void run() throws Exception {
					//morph the picture
					for(int h = 0; h < MORPH_ITERATIONS; h += UPDATE_STEPS ) {
						// MORPH
						//System.out.println("h is "+ h+ " from thread "+ getThreadIndex());
						final int steps = (h + UPDATE_STEPS) > MORPH_ITERATIONS ? last_iteration : UPDATE_STEPS;  

						//have each thread loop through a sub-section of the array1 array
						execute(0, height-1, new IntegerForLoop(){
							public IntegerSchedule schedule(){
								return IntegerSchedule.dynamic(1);
								//return IntegerSchedule.guided(32);
							}
							@Override
							public void run(int start, int finish) throws Exception {
								// send the array1 and array 2 variables between start and finish to be processed.
								//System.out.println("There will be "+ MORPH_ITERATIONS+ " MORPH_ITERATIONS");
								for(int i=0 ; i < steps; i++){
									morphTick(array1, array2, start, finish);
									
									// update the reported timing
									gui.updateTimer();
								}
							}


						},
						new BarrierAction(){
							public void run() {
								//System.out.println("IN THE BARRIER, ALL THREADS ARE @ SAME POINT");
								//Redraw the pictures, for "animation".
								redrawPicture(array1);
		
							}
						});

					}
				}
			});

		} catch (Exception e) {
			e.printStackTrace();
		}
		//redrawPicture(array1);
		// We are done timing now
		gui.stopTimer();

		// Add a done label, if we are using the display
		gui.updateDoneLabel();


		// Saves the last image into a raster
		//int[] arr1D = convertTo1DArray(array1, width, height);
		morphRaster.setPixels(0, 0, width, height, array1);
		return morph;
	}

	// The Morphing code, including display
	// The actual morphing is done in morphTick
	public static BufferedImage serialDoMorph() {

		int last_iteration = MORPH_ITERATIONS % UPDATE_STEPS;

		gui.startTimer();

		for(int h = 0; h < MORPH_ITERATIONS; h += UPDATE_STEPS ) {
			// MORPH
			int steps = (h + UPDATE_STEPS) > MORPH_ITERATIONS ? last_iteration : UPDATE_STEPS;
			for(int i=0 ; i < steps; i++){
				//System.out.println("start is "+ 0 + " finish is "+ (array1.length-1));
				morphTick(array1, array2, 0, height-1);

				// update the reported timing
				gui.updateTimer();
			}	

			// Redraw the pictures, for "animation".
			redrawPicture(array1);
		}


		// We are done timing now
		gui.stopTimer();

		// Add a done label, if we are using the display
		gui.updateDoneLabel();

		// Saves the last image into a raster
		//int[] arr1D = convertTo1DArray(array1, width, height);
		morphRaster.setPixels(0, 0, width, height, array1);

		return morph;
	}

	/**
	 * @param image1 is modified, with each pixel modified to look one
	 *         quantum level (or FAST_STEPPING) more like
	 *         the corresponding pixel in image2.
	 */

	private static void morphTick(int[] image1, int[] image2, int start, int finish) {
		//row

		int element=0;
		//int test[][][] = convertTo3DArray(image1, width,height,tid);
		for(int i = start; i <= finish; i++){
			// column
			for(int j = 0; j < width; j++){

				// colors: red, green and blue
				// calculate the difference between two images for each color


				element = (i * width + j) * 3;
				int r_index = element+0;;
				int g_index = element+1;
				int b_index = element+2;

				int diffr = (image2[r_index] - image1[r_index]);
				int diffg = (image2[g_index] - image1[g_index]);
				int diffb = (image2[b_index] - image1[b_index]);

				//signum() returns -1, 0 or 1.  
				int redstep = (int)Math.signum(diffr);
				int greenstep = (int) Math.signum(diffg);
				int bluestep = (int)Math.signum(diffb);

				int adiffr = Math.abs(diffr);
				int adiffg = Math.abs(diffg);
				int adiffb = Math.abs(diffb);

				//take a big or small step
				if (adiffr >= FAST_STEPPING)
					redstep *= (int) Math.min(adiffr,FAST_STEPPING);
				if (adiffg >= FAST_STEPPING)
					greenstep *= (int) Math.min(adiffg,FAST_STEPPING);
				if (adiffb >= FAST_STEPPING)
					bluestep *= (int) Math.min(adiffb,FAST_STEPPING);

				//update the source image
				image1[r_index] += redstep;
				image1[g_index] += greenstep;
				image1[b_index] += bluestep;

			}
		}

	}

    // Unpack a 1D image array into a 3D array
    public static int[][][] convertTo3DArray( int[] oneDPix1, int width, int height){
   	 
       int[][][] data = new int[height][width][3]; 

       // Convert 1D array to 3D array
       for(int row = 0; row < height; row++){
           for(int col = 0; col < width; col++){
               int element = (row * width + col)*3;
               // Red
               data[row][col][0] = oneDPix1[element+0];
               // Green
               data[row][col][1] = oneDPix1[element+1];
               // Blue
               data[row][col][2] = oneDPix1[element+2];
                              
           }
       }
       return data;
   }

	public static int[] convertTo1DArray( int[][][] data, int width, int height){

		// Pack a 3D image array into a 1D array because raster requires 1D int array
		int[] oneDPix = new int[ width * height * 3];

		int cnt = 0;

		for (int row = 0; row < height; row++){
			for (int col = 0; col < width; col++){
				//red
				oneDPix[cnt++] =  data[row][col][0];
				//green
				oneDPix[cnt++] =  data[row][col][1];
				//blue
				oneDPix[cnt++] =  data[row][col][2];
			}
		}

		return oneDPix;
	}
}

